using System;
using System.Collections.Generic;
using System.Collections;
using System.Collections.Specialized;
using System.Text;
using System.DirectoryServices;
using System.Security.Principal;

using Dunnry.DirectoryServices;

namespace Dunnry.Security
{
    public class RoleManager
    {
        DirectoryEntry _rootDSE;

        string _server;
        string _adsPath;
        string _defaultDomain;
        
        //not currently using credentials, but could add them
        //for now, require process to have credentials
        string _username;
        string _password;

        DirectoryType _dirType;

        public RoleManager(string adsPath) : this(adsPath, null, null) { }

        public RoleManager(string adsPath, string username, string password)
        {
            //this is where we bootstrap the process and get all the information we will need for later...
            _adsPath = adsPath;
            _username = username;
            _password = password;            

            _server = LdapUtils.GetServerFromAdsPath(_adsPath);
            if (String.IsNullOrEmpty(_server))
            {
                //we only allow server binds here (or ADAM would be screwed)...
                throw new InvalidOperationException("Server must be specified");
            }

            //should not fail...
            _rootDSE = LdapUtils.CreateDirectoryEntry("rootDSE", _server, ConnectionProtection.None);
            
            _dirType = LdapUtils.GetDirectoryType(_rootDSE);
            if (_dirType == DirectoryType.Unknown)
                throw new InvalidOperationException("Only Active Directory and ADAM are supported");

            //this will include the trailing \ if it works
            if (_dirType == DirectoryType.AD)
                _defaultDomain = GetNetbiosDomainName();
        }

        public string[] GetRolesForUser(string username)
        {
            string[] roles = null;

            if (_dirType == DirectoryType.AD)
            {
                roles = GetRolesForUserAD(username);
            }

            if (_dirType == DirectoryType.ADAM)
            {
                //throw new NotImplementedException("Not yet ready for ADAM");
                roles =  GetRolesForUserADAM(username);
            }

            //always return something...
            return roles != null ? roles : new string[] { };
        }

        /// Gets the user's token groups and returns them in NT4 account format
        /// (domain\name).  Takes pretty much any parsable user name format.
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
 
        private string[] GetRolesForUserAD(string username)
        {
            DirectoryEntry user = FindUser(_adsPath, username);

            user.RefreshCache(new string[] { "tokenGroups" });

            IdentityReferenceCollection irc = new IdentityReferenceCollection(user.Properties["tokenGroups"].Count);
            foreach (byte[] sidBytes in user.Properties["tokenGroups"])
            {
                irc.Add(new SecurityIdentifier(sidBytes, 0));
            }

            StringCollection coll = new StringCollection();
            irc = irc.Translate(typeof(NTAccount));

            foreach (IdentityReference ir in irc)
            {
                //protects against failed conversions
                if (ir is NTAccount)
                {
                    coll.Add(ir.ToString());
                }
            }
            string[] accounts = new string[coll.Count];

            coll.CopyTo(accounts, 0);
            return accounts;            
        }

        /// <summary>
        /// Returns roles from ADAM.  The input username must be either the ADAM upn (any format)
        /// or the DN of the user object.  Any other input will fail
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private string[] GetRolesForUserADAM(string username)
        {
            DirectoryEntry user = FindUser(_adsPath, username);

            if (user == null)
                throw new ArgumentException("User not found in ADAM");

            user.RefreshCache(new string[] { "tokenGroups" });

            StringBuilder sb = new StringBuilder();
            sb.Append("(|");
            foreach (byte[] sid in user.Properties["tokenGroups"])
            {
                sb.AppendFormat("(objectSid={0})", LdapUtils.BuildFilterOctetString(sid));
            }
            sb.Append(")");

            DirectoryEntry searchRoot = LdapUtils.CreateDirectoryEntry(
                LdapUtils.GetDefaultADAMPartition(_adsPath, _rootDSE),
                _server,
                ConnectionProtection.Secure,
                _username,
                _password
                );

            using (searchRoot)
            {
                DirectorySearcher ds = new DirectorySearcher(
                    searchRoot,
                    sb.ToString(),
                    new string[]{"distinguishedName"},
                    SearchScope.Subtree
                    );

                using (SearchResultCollection src = ds.FindAll())
                {
                    string[] roles = new string[src.Count];
                    int i = 0;
                    foreach (SearchResult sr in src)
                    {
                        roles[i++] = sr.Properties["distinguishedName"][0].ToString();
                    }

                    if (roles.Length > 0)
                        return roles;
                }
            }

            //default return
            return null;
        }

        //try to find our user in ADAM or ADAM
        private DirectoryEntry FindUser(string searchPath, string username)
        {
            string filter = String.Format("(|(userPrincipalName={0})(distinguishedName={0}))", username);

            if (_dirType == DirectoryType.AD)
                filter = String.Format("(|(userPrincipalName={0})(sAMAccountName={0}))", username);

            DirectoryEntry searchRoot = new DirectoryEntry(searchPath, _username, _password, AuthenticationTypes.Secure);

            using (searchRoot)
            {
                DirectorySearcher ds = new DirectorySearcher(
                    searchRoot,
                    filter,
                    null,
                    SearchScope.Subtree
                    );

                SearchResult sr = ds.FindOne();

                return sr != null ? sr.GetDirectoryEntry() : null;
            }
        }

        private string GetNetbiosDomainName()
        {
            string dnc = (string)_rootDSE.Properties["defaultNamingContext"].Value;
            string cnc = (string)_rootDSE.Properties["configurationNamingContext"].Value;

            DirectoryEntry searchRoot = LdapUtils.CreateDirectoryEntry(
                cnc,
                _server,
                ConnectionProtection.Secure,
                _username,
                _password
                );

            using (searchRoot)
            {
            	DirectorySearcher ds = new DirectorySearcher(
                    searchRoot,
                    String.Format("(&(objectCategory=crossRef)(nCName={0}))",dnc),
                    new string[] {"nETBIOSName"},
                    SearchScope.Subtree
                    );

                SearchResult sr = ds.FindOne();
                if (sr != null)
                {
                    if (sr.Properties.Contains("nETBIOSName"))
                    {
                        return sr.Properties["nETBIOSName"][0].ToString();
                    }
                }
            }
            return null;
        }

        
        
    }
}
